package com.sheng.project.aspect;

import com.sheng.project.utils.RandomIdGenerator;
import com.sheng.project.utils.ReflectionKit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StopWatch;

import javax.annotation.Resource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author by ls
 * @date 2025/10/28
 */
@Slf4j
@Aspect
@Order(1)
@Component
public class DistributedLockAspect {

    private static final String GETTER_PREFIX = "get";

    private static final String LUA_UNLOCK_SCRIPT =
            "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";


    @Resource
    private StringRedisTemplate redisTemplate;

    @Around("@annotation(com.sheng.project.aspect.DistributedLock) && @annotation(distributedLock)")
    public Object doAround(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 组成 key
        String lockKey = buildLockKey(distributedLock, joinPoint);
        // 获取和释放分布式锁
        String lockValue = RandomIdGenerator.getUUID();
        try {
            boolean locked = Boolean.TRUE.equals(this.redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, distributedLock.expireSeconds(), TimeUnit.SECONDS));
            stopWatch.stop();
            log.info("[{}]：获取分布式锁：[{}]耗时[{}]ms", method.getName(), lockKey, stopWatch.getTotalTimeMillis());
            Assert.isTrue(locked, "请在" + distributedLock.expireSeconds() + "秒后再试");

            // 执行方法
            return joinPoint.proceed();
        } catch (Throwable throwable) {
            throw throwable;
        } finally {
            try {
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(LUA_UNLOCK_SCRIPT, Long.class);
                Long result = this.redisTemplate.execute(redisScript, Collections.singletonList(lockKey), lockValue);
                boolean released = !ObjectUtils.nullSafeEquals(0L, result);
                log.info("[{}]：释放分布式锁：[{}]", method.getName(), lockKey);
            } catch (Exception e) {
                log.error("释放分布式锁失败：", e);
            }
        }
    }

    /**
     * 构建分布式锁的键
     *
     * @param distributedLock 分布式锁注解
     * @param joinPoint       切点
     * @return 锁键
     */
    private String buildLockKey(DistributedLock distributedLock, ProceedingJoinPoint joinPoint) {
        if (distributedLock.fieldNames().length == 0) {
            return distributedLock.prefix();
        }
        List<String> fieldValues = Arrays.stream(distributedLock.fieldNames())
                .map(fieldName -> getFieldValue(joinPoint, fieldName))
                .collect(Collectors.toList());
        return distributedLock.prefix().concat(StringUtils.join(fieldValues, ""));
    }

    /**
     * 获取字段值
     *
     * @param joinPoint 切点
     * @param fieldName 字段名
     * @return 字段值
     */
    private String getFieldValue(ProceedingJoinPoint joinPoint, String fieldName) {
        String getterMethodName = GETTER_PREFIX + StringUtils.capitalize(fieldName);
        Object fieldValue = null;
        boolean typeOfArgsImplementsDistributedLockable = false;
        for (Object arg : joinPoint.getArgs()) {
            log.info("入参{}：", arg);
            if (arg instanceof DistributedLockable) {
                typeOfArgsImplementsDistributedLockable = true;
                try {
                    Method getter = ReflectionKit.getAccessibleMethod(arg.getClass(), getterMethodName, (Class<?>) null);
                    fieldValue = getter.invoke(arg);
                } catch (IllegalAccessException e) {
                    log.error("IllegalAccessException 发生异常：", e);
                } catch (InvocationTargetException e) {
                    log.error("InvocationTargetException 发生异常：", e);
                }
                if (!ObjectUtils.isEmpty(fieldValue)
                        && (fieldValue instanceof String
                        || fieldValue instanceof Byte
                        || fieldValue instanceof Integer
                        || fieldValue instanceof Long)) {
                    break;
                }
            }
        }
        Assert.isTrue(typeOfArgsImplementsDistributedLockable, "none of the args' type implements " + DistributedLockable.class);
        Assert.isTrue(!ObjectUtils.isEmpty(fieldValue), "Field(name:" + fieldName + ";type:String,Byte,Integer or Long) is null.");
        return String.valueOf(fieldValue);
    }
}
